/**
 * AbstractLoader.java ##copyright## This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option) any later version.
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details. You should have received a copy of the GNU Lesser
 * General Public License along with this library; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 * 
 * @author ##author##
 * @modified ##date##
 * @version ##version##
 */
package fiveagon.loader;

import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import processing.core.PApplet;

/**
 * Abstract implementation of <code>interface Loader&lt;E&gt;</code>. This class creates an
 * immutable Object that cannot be modified after construction. This class only gets a "snapshot" of
 * your data folder at the time of its creation and will <em>not</em> know if the contents change in
 * any way.
 * <p>
 * This class is designed with the <a
 * href="http://en.wikipedia.org/wiki/Template_method_pattern">Template method pattern</a> in mind.
 * Subclasses will implement two abstract methods plus an optional third. These methods are
 * <strong>not</strong> public; they will be called by the methods of the <code>Loader</code>
 * interface.
 * </p>
 * 
 * <pre>
 * protected abstract boolean isLoadable(File file);
 * 
 * protected abstract E loadElement(File file);
 * 
 * protected boolean searchMatches(String search, File file) {
 * }
 * </pre>
 * <p>
 * The method <code>searchMatches</code> is not actually abstract, and so does not need to be
 * implemented. This implementation uses <code>String.contains()</code> logic on the file name,
 * similar to Spotlight on OS X.
 * </p>
 * 
 * @param <E> the Generic type that this object will load.
 */
public abstract class AbstractLoader<E> implements Loader<E> {
  private final File[] fileArray;

  /**
   * Constructor for general use. This <code>Loader</code> will load from the sketch's default data
   * folder.
   * 
   * @param parent a reference to the sketch, usually passed with <code>this</code>.
   * @throws IllegalArgumentException if the sketch's default data folder does not exist.
   */
  public AbstractLoader(PApplet parent) {
    this(parent.dataFile("")); // Processing 2.0
    // this(new File(parent.sketchPath, "data")); // Processing 1.5
  }

  /**
   * Constructor for subclasses that want to load from any one folder.
   * 
   * @param dataFolder the folder to load files from.
   * @throws IllegalArgumentException if <code>dataFolder</code> is not a directory.
   */
  public AbstractLoader(File dataFolder) {
    this.fileArray = setupFiles(dataFolder);
  }

  private File[] setupFiles(File dataFolder) {
    if (dataFolder.isDirectory()) {
      FileFilter fileFilter = new FileFilter() {
        public boolean accept(File file) {
          return isLoadable(file);
        }
      };
      return dataFolder.listFiles(fileFilter);
    } else {
      throw new IllegalArgumentException('"' + dataFolder.getPath()
          + "\" is not a directory, or just doesn't exist.");
    }
  }

  public final List<E> load() {
    List<E> elementList = new ArrayList<E>();
    for (File file : fileArray) {
      elementList.add(loadElement(file));
    }
    return elementList;
  }

  public final List<E> load(String search) {
    List<E> elementList = new ArrayList<E>();
    for (File file : fileArray) {
      if (searchMatches(search, file)) {
        elementList.add(loadElement(file));
      }
    }
    return elementList;
  }

  /**
   * Gets a single Element that matches a <code>search</code>.
   * 
   * @param search the string to match against files
   * @return the first file loaded into an <code>Element</code> that matches the <code>search</code>
   *         , else <code>null</code>
   * @throws IllegalArgumentException if nothing matched <code>search</code>
   */
  public final E loadSingle(String search) {
    for (File file : fileArray) {
      if (searchMatches(search, file)) {
        return loadElement(file); // return the first match
      }
    }
    throw new IllegalArgumentException("Nothing matched: \"" + search + '"');
  }

  /**
   * Returns a string representation of all files that can be loaded by this Loader. A call to
   * <code>load()</code> will load every one of these files. Use this loader as the arguments to
   * <code>println</code>.
   * 
   * @see PApplet#println(Object)
   * @see System#out
   */
  @Override
  public String toString() {
    return Arrays.toString(fileArray);
  }

  /**
   * Abstract method for determining if a search String "matches" a particular file. Implementations
   * of this method do not need to take into account the file extension of the file, as that is
   * usually taken care of in the isLoadable(File) method.
   * 
   * @param search the search to match against a file.
   * @param file the particular file being tested.
   * @return true if the search matches the file
   */
  protected boolean searchMatches(String search, File file) {
    return file.getName().contains(search); // Override if you need to
  }

  /**
   * Abstract method for determining if this Loader can open a certain file. This usually involves
   * getting the name of the file and seeing if it ends with the right file type.
   * 
   * @param file the file we are attempting to load from.
   * @return true if <code>file</code> can be loaded.
   */
  protected abstract boolean isLoadable(File file);

  /**
   * Abstract method for loading a Generic Element from a specific file.
   * 
   * @param file the file we are attempting to load from.
   * @return the file loaded into the desired Generic type.
   */
  protected abstract E loadElement(File file);
}
